#include "StdAfx.h"
#include "MenuRender3DModelMgr.h"
#include "ISystem.h"
#include "IGameRulesSystem.h"

CMenuRender3DModelMgr * CMenuRender3DModelMgr::s_instance = NULL;
CMenuRender3DModelMgr * CMenuRender3DModelMgr::s_firstNonFreedModelMgr = NULL;
float CMenuRender3DModelMgr::s_lightRotate = 0.f;
uint8 CMenuRender3DModelMgr::s_numActualInstances = 0;

const float kIdealHeightWidthRatio = 0.5625f; // Height over width of a resolution with no gaps around the Flash front end e.g. 1280x720 (720/1280 = 0.5625)

//========================================================================
CMenuRender3DModelMgr::CMenuRender3DModelMgr(SSettings settings) :
m_scaleVector(1.f, 1.f, 1.f)
{
	CMenuRender3DModelMgr::Release();

	if (++ s_numActualInstances == 1)
	{
		gEnv->p3DEngine->SetPostEffectParam("Post3DRenderer_Active",1.0f,true);
	}

	CryLog ("[MenuRender3DModelMgr] Creating instance %p THREAD=%u", this, CryGetCurrentThreadId());
	INDENT_LOG_DURING_SCOPE();

	assert (s_instance == NULL);
	s_instance = this;

	m_useTexture = gEnv->pRenderer->EF_LoadTexture("$BackBufferScaled_d2");

	memcpy (& m_settings, & settings, sizeof(settings));
	m_numRenderEntities = 0;
	m_alpha = 0.f;
	m_fractionPushedBackwardsBecauseOfWarning = 0.f;

#if MENURENDER3DMODELMGR_DOLIGHTS
	m_lightSource = gEnv->p3DEngine->CreateLightSource();
	assert (m_lightSource);

	CryLog ("[MenuRender3DModelMgr] Created instance %p - texture=%p, lightSource=%p", this, m_useTexture, m_lightSource);

	UpdateLightSettings(0.f);	//Needs to update before Register so it has a valid AABB
	gEnv->p3DEngine->RegisterEntity(m_lightSource);

#else
	CryLog ("[MenuRender3DModelMgr] Created instance %p - texture=%p", this, m_useTexture);
#endif

	m_releaseMe = false;
	m_framesUntilReadyToAppear = 4;
	m_framesUntilDestroy = 3; // Should be < m_framesUntilReadyToAppear, otherwise can get multiple lights
	m_nextNonFreedModelMgr = NULL;
	m_prevNonFreedModelMgr = NULL;

	gEnv->pGame->GetIGameFramework()->RegisterListener(this, "MenuRender3DModelMgr", eFLPriority_HUD);

	m_entityCurrentRotation = Ang3(0,0,0);
}

//========================================================================
void CMenuRender3DModelMgr::Release(bool immediateDelete)
{
	if (s_instance)
	{
		assert (! s_instance->m_releaseMe);
		s_instance->m_releaseMe = true;

		if (gEnv->pInput)
		{
			gEnv->pInput->RemoveEventListener(s_instance);
		}

		assert (s_instance->m_nextNonFreedModelMgr == NULL);
		assert (s_instance->m_prevNonFreedModelMgr == NULL);

		s_instance->SetVisibilityOfAllEntities(false);
		if (s_firstNonFreedModelMgr)
		{
			assert (s_firstNonFreedModelMgr->m_prevNonFreedModelMgr == NULL);
			s_firstNonFreedModelMgr->m_prevNonFreedModelMgr = s_instance;
		}
		s_instance->m_nextNonFreedModelMgr = s_firstNonFreedModelMgr;
		s_firstNonFreedModelMgr = s_instance;
		s_instance = NULL;
	}

	if (immediateDelete)
	{
		while (s_firstNonFreedModelMgr)
		{
			assert (s_firstNonFreedModelMgr->m_prevNonFreedModelMgr == NULL);
			delete s_firstNonFreedModelMgr;
		}
	}
}

//========================================================================
CMenuRender3DModelMgr::~CMenuRender3DModelMgr()
{
	assert (s_numActualInstances);

	if (-- s_numActualInstances == 0)
	{
		assert (m_prevNonFreedModelMgr == NULL);
		assert (m_nextNonFreedModelMgr == NULL);
		gEnv->p3DEngine->SetPostEffectParam("Post3DRenderer_Active",0.0f,true);
	}

	assert (m_releaseMe);

	CryLog ("[MenuRender3DModelMgr] Destroying instance %p (%d entities) THREAD=%u", this, m_numRenderEntities, CryGetCurrentThreadId());
	INDENT_LOG_DURING_SCOPE();

	if (m_nextNonFreedModelMgr)
	{
		assert (m_nextNonFreedModelMgr->m_prevNonFreedModelMgr == this);
		m_nextNonFreedModelMgr->m_prevNonFreedModelMgr = m_prevNonFreedModelMgr;
	}

	if (m_prevNonFreedModelMgr == NULL)
	{
		assert (s_firstNonFreedModelMgr == this);
		s_firstNonFreedModelMgr = m_nextNonFreedModelMgr;
	}
	else
	{
		assert (s_firstNonFreedModelMgr != this);
		assert (m_prevNonFreedModelMgr->m_nextNonFreedModelMgr == this);
		m_prevNonFreedModelMgr->m_nextNonFreedModelMgr = m_nextNonFreedModelMgr;
	}

	for (uint8 i = 0; i < m_numRenderEntities; ++ i)
	{
		gEnv->pEntitySystem->RemoveEntity(m_renderEntityData[i].m_entityId, true);
	}

	gEnv->pGame->GetIGameFramework()->UnregisterListener(this);

	SAFE_RELEASE(m_useTexture);

#if MENURENDER3DMODELMGR_DOLIGHTS
	gEnv->p3DEngine->UnRegisterEntity(m_lightSource);
	gEnv->p3DEngine->DeleteLightSource(m_lightSource);
	m_lightSource = NULL;
#endif
}

//========================================================================
CMenuRender3DModelMgr::TAddedModelIndex CMenuRender3DModelMgr::AddModel(const char * filename, const char * materialFilename, bool isCharacter)
{
	TAddedModelIndex bAddedAsIndex = kAddedModelIndex_Invalid;

	assert (filename);
	assert (materialFilename);

	if (m_numRenderEntities < kAddedModelIndex_MaxEntities && filename[0])
	{
		CryLog ("[MenuRender3DModelMgr] Adding %s model='%s' material='%s' THREAD=%u", isCharacter ? "character" : "prop", filename, materialFilename, CryGetCurrentThreadId());
		INDENT_LOG_DURING_SCOPE();

		const CCamera &camera = gEnv->pSystem->GetViewCamera();

		Vec3 cameraPos = camera.GetPosition();
		const Matrix34 & cameraMatrix = camera.GetMatrix();
		Vec3 playerPos = cameraMatrix * m_settings.m_entityBasePosition;
		float moveSpeed = 1.0f;
		float rotSpeed = 1.0f;
		bool setPos = true;
		Quat rot;
		rot.SetRotationXYZ(m_settings.m_entityRot);
		Quat cameraRot(camera.GetViewMatrix());
		rot = cameraRot.GetInverted() * rot;

		char createWithName[32];
		sprintf(createWithName, "Menu%s%d", isCharacter ? "Char" : "Prop", m_numRenderEntities);

		SEntitySpawnParams entitySpawnParams;
		entitySpawnParams.pClass = gEnv->pEntitySystem->GetClassRegistry()->GetDefaultClass();
		entitySpawnParams.sName = createWithName;
		entitySpawnParams.nFlags = ENTITY_FLAG_NO_PROXIMITY | ENTITY_FLAG_CLIENT_ONLY | ENTITY_FLAG_NO_SAVE;
		entitySpawnParams.vPosition = Vec3(0.f, 0.f, 0.f);
		entitySpawnParams.qRotation = rot;
		IEntity * newlyCreatedEntity = gEnv->pEntitySystem->SpawnEntity(entitySpawnParams);

		if(newlyCreatedEntity)
		{
			newlyCreatedEntity->Hide(true);

			if (isCharacter)
			{
				newlyCreatedEntity->LoadCharacter(0, filename);
			}
			else
			{
				newlyCreatedEntity->LoadGeometry(0, filename);
			}

			if (materialFilename[0])
			{
				IMaterial *pReplacement = gEnv->p3DEngine->GetMaterialManager()->FindMaterial(materialFilename);
				if (!pReplacement)
				{
					pReplacement = gEnv->p3DEngine->GetMaterialManager()->LoadMaterial(materialFilename, false);

					if (!pReplacement)
					{
						CryLogAlways("Material not found '%s'", materialFilename);
					}
				}

				newlyCreatedEntity->SetSlotMaterial(0, pReplacement);
			}

			if(IEntityRenderProxy* pRenderProxy = static_cast<IEntityRenderProxy*>(newlyCreatedEntity->GetProxy(ENTITY_PROXY_RENDER)))
			{
				if(pRenderProxy)
				{
					pRenderProxy->SetAsPost3dRenderObject(true);

					// Precache material -> This forces the material to have a high priority with streaming
					if(isCharacter)
					{
						SEntitySlotInfo slotInfo;
						bool gotSlotInfo = newlyCreatedEntity->GetSlotInfo(0, slotInfo);
						if(gotSlotInfo && slotInfo.pCharacter)
						{
							IAttachmentManager* pAttachmentMgr = slotInfo.pCharacter->GetIAttachmentManager();
							if(pAttachmentMgr)
							{
								int32 attachmentCount = pAttachmentMgr->GetAttachmentCount();
								for(int32 i=0; i<attachmentCount; i++)
								{
									IAttachment* pAttachment = pAttachmentMgr->GetInterfaceByIndex(i);
									if(pAttachment)
									{
										IAttachmentObject* pAttachmentObj = pAttachment->GetIAttachmentObject();
										if(pAttachmentObj)
										{
											IMaterial* pMaterialToCache = pAttachmentObj->GetMaterial();
											if(pMaterialToCache)
											{
												pMaterialToCache->PrecacheMaterial(0.0f,NULL,true,true);
											}
										}
									}
								}
							}
						}
					}
					else
					{
						IMaterial* pMaterialToCache = pRenderProxy->GetRenderMaterial();
						if(pMaterialToCache)
						{
							pMaterialToCache->PrecacheMaterial(0.0f,NULL,true,true);
						}
					}

					IRenderNode* pRenderNode = pRenderProxy->GetRenderNode();
					if(pRenderNode)
					{
						pRenderNode->SetRndFlags(ERF_RENDER_ALWAYS,true);
					}
				}
			}

			m_renderEntityData[m_numRenderEntities].m_entityId = newlyCreatedEntity->GetId();
			m_renderEntityData[m_numRenderEntities].m_isCharacter = isCharacter;
			bAddedAsIndex = m_numRenderEntities;
			++ m_numRenderEntities;

			if (bAddedAsIndex == 0)
			{
				AABB localBounds;
				newlyCreatedEntity->GetLocalBounds(localBounds);
				CryLog ("Added '%s' as top-level 3D menu entity, bounds = (%f %f %f) to (%f %f %f)", filename, localBounds.min.x, localBounds.min.y, localBounds.min.z, localBounds.max.x, localBounds.max.y, localBounds.max.z);
				Vec3 centreIt = localBounds.GetCenter();
				const float modelLength = localBounds.max.y - localBounds.min.y; // Local Y axis is looking down the barrel - i.e. how long the weapon model is



				const float k_scale = 3.0f;

				const float modelHeightScaled = (localBounds.max.z - localBounds.min.z) * k_scale; // Local Z axis is height (scale this because being tall is more of a reason to shrink the model than being long, as the screen's less tall than it is wide)
				const float shrinkUsingSize = max(modelHeightScaled, modelLength);

				if (shrinkUsingSize > 0.f)
				{
					const float scale = 1.f / powf(shrinkUsingSize, 0.75f);
					m_scaleVector = Vec3(scale, scale, scale);
					centreIt *= scale;
				}
				newlyCreatedEntity->SetPos(playerPos - centreIt * rot.GetInverted());
			}

			if (m_settings.m_doScaling)
			{
				newlyCreatedEntity->SetScale(m_scaleVector);
			}
		}
	}

	return bAddedAsIndex;
}

//========================================================================
void CMenuRender3DModelMgr::SetAnim(const char * animName, TAddedModelIndex modelIndex, float speedMultiplier, int layer)
{
	if (modelIndex < m_numRenderEntities && modelIndex < kAddedModelIndex_MaxEntities)
	{
		IEntity * pEntity = gEnv->pEntitySystem->GetEntity(m_renderEntityData[modelIndex].m_entityId);
		assert (pEntity);

		ICharacterInstance *pCharInst = pEntity->GetCharacter(0);
		if(!pCharInst || !pCharInst->GetISkeletonAnim())
		{
			CryWarning(VALIDATOR_MODULE_GAME, VALIDATOR_WARNING, "[Model3DHUD] Trying to set animation on invalid character");
			return;
		}

		CryLog ("Setting animation '%s' on '%s' \"%s\"", animName, pEntity->GetName(), pCharInst->GetFilePath());

		CryCharAnimationParams params;
		params.m_nLayerID = layer;
		params.m_nFlags |= CA_LOOP_ANIMATION;
		params.m_fPlaybackSpeed = speedMultiplier;
		params.m_fTransTime = 0.0f;
		pCharInst->GetISkeletonAnim()->StartAnimation(animName, params);
	}

}

//========================================================================
void CMenuRender3DModelMgr::AttachToStaticObject(TAddedModelIndex parentIndex, TAddedModelIndex childIndex, const char * mountPointName)
{
	if (parentIndex < m_numRenderEntities && childIndex < m_numRenderEntities)
	{
		IEntity * parentEntity = gEnv->pEntitySystem->GetEntity(m_renderEntityData[parentIndex].m_entityId);
		IEntity * childEntity = gEnv->pEntitySystem->GetEntity(m_renderEntityData[childIndex].m_entityId);
		assert (parentEntity);
		assert (childEntity);

		IStatObj * pTPObject = parentEntity->GetStatObj(0);
		const Vec3 rawPosition = pTPObject->GetHelperPos(mountPointName);
		CryLog ("Attaching %s to %s at (%f %f %f)", childEntity->GetName(), parentEntity->GetName(), rawPosition.x, rawPosition.y, rawPosition.z);
		const Vec3 transformedPosition = childEntity->GetSlotLocalTM(0, false).TransformPoint(rawPosition);

		parentEntity->AttachChild(childEntity);
		Matrix34 tm(Matrix34::CreateIdentity());
		tm.SetTranslation(transformedPosition);
		childEntity->SetLocalTM(tm);
	}
}

//========================================================================
void CMenuRender3DModelMgr::AttachToCharacter(TAddedModelIndex parentIndex, TAddedModelIndex childIndex, const char * attachHere)
{
	assert (attachHere);
	assert (attachHere[0]);

	if (parentIndex < m_numRenderEntities && childIndex < m_numRenderEntities)
	{
		IEntity * parentEntity = gEnv->pEntitySystem->GetEntity(m_renderEntityData[parentIndex].m_entityId);
		assert (parentEntity);

		ICharacterInstance *pCharInst = parentEntity->GetCharacter(0);
		assert (pCharInst);

		// Attach primary weapon to character
		IAttachmentManager * attachMgr = pCharInst->GetIAttachmentManager();
		IAttachment *pAttachment = attachMgr->GetInterfaceByName(attachHere);

		if (pAttachment)
		{
			CEntityAttachment *pEntityAttachment = new CEntityAttachment();
			pEntityAttachment->SetEntityId(m_renderEntityData[childIndex].m_entityId);

			pAttachment->AddBinding(pEntityAttachment);
			pAttachment->HideAttachment(0);
		}
#ifndef _RELEASE
		else
		{
			CryLog ("Failed to find attachment interface called '%s' - here are the ones which exist:", attachHere);
			INDENT_LOG_DURING_SCOPE();
			int32 numAttachmentPositions = attachMgr->GetAttachmentCount();
			for (int32 index = 0; index < numAttachmentPositions; ++ index)
			{
				CryLog ("%s", attachMgr->GetInterfaceByIndex(index)->GetName());
			}
			GameWarning("!Failed to attach 3D menu entities together using attachment position called '%s' - check log for details", attachHere);
		}
#endif

	}
}

//========================================================================
#if MENURENDER3DMODELMGR_DOLIGHTS
void CMenuRender3DModelMgr::UpdateLightSettings(float dt)
{
	assert (m_lightSource);

	s_lightRotate += dt * 3.f;

	while (s_lightRotate >= gf_PI)
	{
		s_lightRotate -= (gf_PI2);
	}

	CDLight menuLight;

	const CCamera& camera = gEnv->p3DEngine->GetCurrentCamera();
	const Matrix34 & cameraMatrix = camera.GetMatrix();
	const Vec3 lightPos = cameraMatrix * m_settings.m_lightOrbitPosition;

	Vec3 menuLightPos = lightPos + m_settings.m_lightOrbitDistance * Vec3(sinf(s_lightRotate), 0.0f, cosf(s_lightRotate));
	ColorF menuLightColor(m_settings.m_lightColourRed,m_settings.m_lightColourGreen,m_settings.m_lightColourBlue,0.0);

	menuLight.SetPosition( menuLightPos );
	menuLight.SetLightColor(menuLightColor);
	menuLight.SetSpecularMult(m_settings.m_lightSpecularMult);

	menuLight.m_fRadius = 400.f;
	menuLight.m_Flags |= DLF_POINT|DLF_POST_3D_RENDERER;
	menuLight.m_sName = "menu light";

//	CryLog ("[MenuRender3DModelMgr] Update light settings (this=%p, lightSource=%p) THREAD=%u", this, m_lightSource, CryGetCurrentThreadId());

	m_lightSource->SetLightProperties(menuLight);

	Vec3 halfBBSize(menuLight.m_fRadius,menuLight.m_fRadius,menuLight.m_fRadius);

	m_lightSource->SetBBox(AABB(menuLightPos-halfBBSize,menuLightPos+halfBBSize));

	gEnv->p3DEngine->SetPostEffectParamVec4( "Post3DRenderer_Ambient", m_settings.m_ambientLight, true );
}
#endif

//========================================================================
void CMenuRender3DModelMgr::RealignAllEntitiesWithCamera()
{
	// Currently only need to move entity 0 as all other entities will be attached to it... [TF]
	if (m_numRenderEntities)
	{
		IEntity * pEntity = gEnv->pEntitySystem->GetEntity(m_renderEntityData[0].m_entityId);
		if (pEntity)
		{
			AABB localBounds;
			pEntity->GetLocalBounds(localBounds);
			const Vec3 centreIt = localBounds.GetCenter() * m_scaleVector.x;

			const CCamera &camera = gEnv->pSystem->GetViewCamera();
			const Matrix34 & cameraMatrix = camera.GetMatrix();
			const Vec3 cameraPos = cameraMatrix.GetColumn3();
			const Vec3 playerPos = cameraMatrix * m_settings.m_entityBasePosition;

			QuatT newTran;
			newTran.q.SetRotationXYZ(m_settings.m_entityRot);

			// world transform with the continuous rotation
			QuatT finalRot; finalRot.SetIdentity();
			QuatT deltaRot; deltaRot.SetIdentity();
			QuatT worldRot; worldRot.SetIdentity();
			deltaRot.q.SetRotationXYZ(m_entityCurrentRotation);
			worldRot.q *= deltaRot.q;
			finalRot.q = worldRot.q;
			finalRot.q *= newTran.q;
	
			newTran.q = finalRot.q;

			const Quat cameraRot(cameraMatrix);
			newTran.q = cameraRot * newTran.q;
			newTran.t = playerPos - centreIt * newTran.q.GetInverted();

			pEntity->SetPos(newTran.t);
			pEntity->SetRotation(newTran.q);

			ICharacterInstance *pCharInst = pEntity->GetCharacter(0);
			if (pCharInst)
			{
				pCharInst->UpdateAttachedObjectsFast(newTran, 1.0f, 0);
			}

			IRenderer *pRenderer = gEnv->pRenderer;

			float fovModifier;
			const float actualScreenRatio = pRenderer->GetHeight() / (float)pRenderer->GetWidth();

			if (m_settings.m_doTextureSideways)
			{
				fovModifier = pRenderer->GetPixelAspectRatio() * actualScreenRatio;
			}
			else
			{
				fovModifier = pRenderer->GetPixelAspectRatio() * (actualScreenRatio / kIdealHeightWidthRatio);
			}

			gEnv->p3DEngine->SetPostEffectParam("Post3DRenderer_FOVScale", m_settings.m_fovScale * fovModifier, true);
		}
	}
}

//========================================================================
void CMenuRender3DModelMgr::OnPreRender()
{
	if (! m_releaseMe)
	{
		RealignAllEntitiesWithCamera();
	}
}

//========================================================================
void CMenuRender3DModelMgr::SetVisibilityOfAllEntities(bool visible)
{
	for (uint8 n = 0; n < m_numRenderEntities; ++ n)
	{
		IEntity * eachEntity = gEnv->pEntitySystem->GetEntity(m_renderEntityData[n].m_entityId);
		if (eachEntity)
		{
			eachEntity->Hide(! visible);
		}
	}
}

//========================================================================
void CMenuRender3DModelMgr::OnPostUpdate(float dt)
{
	if (m_releaseMe)
	{
		assert (m_framesUntilDestroy);
		if (0 == -- m_framesUntilDestroy)
		{
			delete this;
		}
	}
	else
	{
		const float keepOldValueFraction = powf(0.01f, dt);
		m_entityCurrentRotation += m_settings.m_entityContinuousRot * keepOldValueFraction;
		
		m_fractionPushedBackwardsBecauseOfWarning = (false ? 1.f : 0.f) * (1.f - keepOldValueFraction) + m_fractionPushedBackwardsBecauseOfWarning * keepOldValueFraction;

#if MENURENDER3DMODELMGR_DOLIGHTS
		UpdateLightSettings(dt);
#endif

		if (m_framesUntilReadyToAppear == 0)
		{
			const float alphaPlusDt = m_alpha + dt * m_settings.m_fadeInSpeed;
			m_alpha = min(alphaPlusDt, 1.f);
			IRenderer * pRenderer = gEnv->pRenderer;
			const float actualScreenRatio = pRenderer->GetHeight() / (float)pRenderer->GetWidth();
			const float actualScreenRatioComparedToWidescreen = kIdealHeightWidthRatio / actualScreenRatio;
			float scaleWidth = 1.f - m_fractionPushedBackwardsBecauseOfWarning;
			float scaleHeight = scaleWidth;

			if (m_settings.m_doTextureSideways)
			{
				scaleWidth /= pRenderer->GetPixelAspectRatio();
				scaleHeight *= pRenderer->GetPixelAspectRatio();
			}

			const float centreX = m_settings.m_imageCentreX;
			const float centreY = ((m_settings.m_imageCentreY / 600.f - 0.5f) * (actualScreenRatioComparedToWidescreen) + 0.5f) * 600.f;
			const float width = m_settings.m_imageWidth * scaleWidth;
			const float height = m_settings.m_imageHeight * scaleHeight;
			const float x1 = centreX - width * 0.5f;
			const float y1 = centreY - height * 0.5f;

			pRenderer->SetState(GS_BLSRC_SRCALPHA | GS_BLDST_ONEMINUSSRCALPHA | GS_NODEPTHTEST);
			pRenderer->Push2dImage(x1, y1, width, height, m_useTexture->GetTextureID(), 0.f, 1.f, 1.f, 0.f, m_settings.m_doTextureSideways ? 90.f : 0.f, 1.f, 1.f, 1.f, m_alpha * m_alpha, m_settings.m_screenDepth);
			pRenderer->Draw2dImageList();
		}
		else
		{
			// TODO: Don't set this to true if still streaming in textures, anims etc.
			if (0 == -- m_framesUntilReadyToAppear)
			{
				SetVisibilityOfAllEntities(true);
			}
		
		}
	}
}